package io.gitee.skyarthur1987.leporidae.delayqueue.autoconfigure;

import io.lettuce.core.resource.ClientResources;
import io.lettuce.core.resource.DefaultClientResources;
import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.data.redis.RedisProperties;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.RedisPassword;
import org.springframework.data.redis.connection.RedisStandaloneConfiguration;
import org.springframework.data.redis.connection.lettuce.LettuceClientConfiguration;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.connection.lettuce.LettucePoolingClientConfiguration;
import org.springframework.data.redis.core.StringRedisTemplate;

/**
 * redis lettuce 连接配置
 * Created by skyarthur on 2023/1/27
 */
@Configuration
@ConditionalOnProperty(name = "leporidae.delay-queue.enable", havingValue = "true")
public class LeporidaeLettuceConnectionConfiguration {

    @ConfigurationProperties(prefix = "leporidae.redis")
    @Bean("globalRedisProperties")
    public RedisProperties globalRedisProperties() {
        return new RedisProperties();
    }

    @ConfigurationProperties(prefix = "leporidae.delay-queue.redis")
    @ConditionalOnProperty(name = "leporidae.delay-queue.redis.host")
    @Bean("delayQueueRedisProperties")
    public RedisProperties delayQueueRedisProperties() {
        return new RedisProperties();
    }

    @Bean(destroyMethod = "shutdown")
    @ConditionalOnMissingBean(ClientResources.class)
    public DefaultClientResources lettuceClientResources() {
        return DefaultClientResources.create();
    }

    @Bean
    public StringRedisTemplate stringRedisTemplate(
            @Qualifier("redisLettuceConnectionFactory") RedisConnectionFactory redisConnectionFactory) {
        StringRedisTemplate template = new StringRedisTemplate();
        template.setConnectionFactory(redisConnectionFactory);
        return template;
    }

    @Bean
    public LettuceConnectionFactory redisLettuceConnectionFactory(
            ClientResources clientResources,
            @Qualifier("globalRedisProperties") RedisProperties globalRedisProperties,
            @Autowired(required = false) @Qualifier("delayQueueRedisProperties") RedisProperties delayQueueRedisProperties) {
        RedisProperties redisProperties = globalRedisProperties;
        if (delayQueueRedisProperties != null) {
            redisProperties = delayQueueRedisProperties;
        }
        RedisStandaloneConfiguration standaloneConfiguration = getStandaloneConfig(redisProperties);
        LettuceClientConfiguration clientConfig = getLettuceClientConfiguration(clientResources, redisProperties);
        return new LettuceConnectionFactory(standaloneConfiguration, clientConfig);
    }

    /**
     * redis standalone config
     *
     * @param redisProperties redis 配置参数
     * @return RedisStandaloneConfiguration
     */
    private RedisStandaloneConfiguration getStandaloneConfig(RedisProperties redisProperties) {
        RedisStandaloneConfiguration config = new RedisStandaloneConfiguration();
        config.setHostName(redisProperties.getHost());
        config.setPort(redisProperties.getPort());
        config.setPassword(RedisPassword.of(redisProperties.getPassword()));
        config.setDatabase(redisProperties.getDatabase());
        return config;
    }

    /**
     * 构建 LettuceClientConfiguration
     *
     * @param clientResources clientResources
     * @param redisProperties redisProperties
     * @return LettuceClientConfiguration
     */
    private LettuceClientConfiguration getLettuceClientConfiguration(ClientResources clientResources,
                                                                     RedisProperties redisProperties) {
        LettuceClientConfiguration.LettuceClientConfigurationBuilder builder =
                createBuilder(redisProperties.getLettuce().getPool());
        if (redisProperties.isSsl()) {
            builder.useSsl();
        }
        if (redisProperties.getTimeout() != null) {
            builder.commandTimeout(redisProperties.getTimeout());
        }
        if (redisProperties.getLettuce() != null) {
            RedisProperties.Lettuce lettuce = redisProperties.getLettuce();
            if (lettuce.getShutdownTimeout() != null
                    && !lettuce.getShutdownTimeout().isZero()) {
                builder.shutdownTimeout(
                        redisProperties.getLettuce().getShutdownTimeout());
            }
        }
        builder.clientResources(clientResources);
        return builder.build();
    }

    /**
     * 创建 LettuceClientConfigurationBuilder
     *
     * @param pool 连接池配置
     * @return LettuceClientConfigurationBuilder
     */
    private LettuceClientConfiguration.LettuceClientConfigurationBuilder createBuilder(RedisProperties.Pool pool) {
        if (pool == null) {
            return LettuceClientConfiguration.builder();
        }
        return LettucePoolingClientConfiguration.builder()
                .poolConfig(getPoolConfig(pool));
    }

    /**
     * pool config
     *
     * @param properties redis 参数配置
     * @return GenericObjectPoolConfig
     */
    private GenericObjectPoolConfig<?> getPoolConfig(RedisProperties.Pool properties) {
        GenericObjectPoolConfig<?> config = new GenericObjectPoolConfig<>();
        config.setMaxTotal(properties.getMaxActive());
        config.setMaxIdle(properties.getMaxIdle());
        config.setMinIdle(properties.getMinIdle());
        if (properties.getMaxWait() != null) {
            config.setMaxWait(properties.getMaxWait());
        }
        return config;
    }
}
